/*
This file is part of Caelum.
See http://www.ogre3d.org/wiki/index.php/Caelum 

Copyright (c) 2006-2008 Caelum team. See Contributors.txt for details.

Caelum is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Caelum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with Caelum. If not, see <http://www.gnu.org/licenses/>.
*/

#include "CaelumPrecompiled.h"
#include "CaelumSystem.h"
#include "CaelumExceptions.h"
#include "InternalUtilities.h"
#include "Astronomy.h"
#include "CaelumPlugin.h"
#include "FlatCloudLayer.h"

using namespace Ogre;
using namespace Caelum;

const String CaelumSystem::DEFAULT_SKY_GRADIENTS_IMAGE = "EarthClearSky2.png";
const String CaelumSystem::DEFAULT_SUN_COLOURS_IMAGE = "SunGradient.png";


//-----------------------------------------------------------------------------
///
CaelumSystem::CaelumSystem(Ogre::SceneManager *sceneMgr, CaelumComponent componentsToCreate) :
mSceneMgr            (sceneMgr),
mpCaelumCameraNode   (NULL),
mpCaelumGroundNode   (NULL),
mCleanup             (false),
mpUniversalClock     (NULL)
{
   LogManager::getSingleton().logMessage ("Caelum: Initialising Caelum system...");
   if (!Root::getSingletonPtr())
      OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR, "Ogre Root not exist", "CaelumSystem::CaelumSystem");

   if (!sceneMgr)
      OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "Null SceneManager interface pointer", "CaelumSystem::CaelumSystem");

   if (!CaelumPlugin::getSingletonPtr ())
   {
      LogManager::getSingleton().logMessage ("Caelum: Plugin not installed; installing now.");
      new CaelumPlugin ();
      CaelumPlugin::getSingleton().install();
      CaelumPlugin::getSingleton().initialise();
   }

   // Create scene nodes
   Ogre::String uniqueId = Ogre::StringConverter::toString((size_t)this);
   mpCaelumCameraNode = mSceneMgr->getRootSceneNode()->createChildSceneNode("Caelum/CameraNode/" + uniqueId);
   mpCaelumGroundNode = mSceneMgr->getRootSceneNode()->createChildSceneNode("Caelum/GroundNode/" + uniqueId);

   // Clock
   mpUniversalClock = new UniversalClock();

   // If the "Caelum" resource group does not exist; create it.
   // This resource group is never released; which may be bad.
   // What does ogre do for it's own runtime resources?
   if (!ResourceGroupManager::getSingleton().resourceGroupExists(Caelum::RESOURCE_GROUP_NAME))
   {
      LogManager::getSingleton().logMessage(
         "Caelum: Creating required internal resource group \'" + RESOURCE_GROUP_NAME + "\'");
      ResourceGroupManager::getSingleton().createResourceGroup(Caelum::RESOURCE_GROUP_NAME);
   }

   // Autoconfigure. Calls clear first to set defaults.
   autoConfigure(componentsToCreate);
}


//-----------------------------------------------------------------------------
///
CaelumSystem::~CaelumSystem()
{
   destroySubcomponents(true);
   LogManager::getSingleton ().logMessage ("Caelum: CaelumSystem destroyed.");
}


//-----------------------------------------------------------------------------
///
void CaelumSystem::destroySubcomponents(bool destroyEverything)
{
   // Destroy sub-components
   setSkyDome (0);
   setSun (0);
   setImageStarfield (0);
   setPointStarfield (0);
   setCloudSystem (0);
   setPrecipitationController (0);
   setDepthComposer (0);
   setGroundFog (0);
   setMoon (0);   
   mSkyGradientsImage.reset ();
   mSunColoursImage.reset ();

   // These things can't be rebuilt.
   if (destroyEverything)
   {
      if (mpUniversalClock)
      {
         LogManager::getSingleton ().logMessage("Caelum: Delete UniversalClock");
         delete mpUniversalClock; mpUniversalClock = NULL;
      }

      if (mpCaelumCameraNode)
         mSceneMgr->getRootSceneNode()->removeAndDestroyChild(mpCaelumCameraNode->getName());

      if (mpCaelumGroundNode)
         mSceneMgr->getRootSceneNode()->removeAndDestroyChild(mpCaelumGroundNode->getName());

      mpCaelumCameraNode = mpCaelumGroundNode = NULL;
   }
}



void CaelumSystem::clear()
{
   // Destroy all subcomponents first.
   destroySubcomponents(false);

   // Some "magical" behaviour.
   mAutoMoveCameraNode = true;
   mAutoNotifyCameraChanged = true;
   mAutoAttachViewportsToComponents = true;
   mAutoViewportBackground = true;

   // Default lookups.
   setSkyGradientsImage(DEFAULT_SKY_GRADIENTS_IMAGE);
   setSunColoursImage(DEFAULT_SUN_COLOURS_IMAGE);

   // Fog defaults.
   setManageSceneFog (Ogre::FOG_EXP2);
   setManageSceneFogStart(900);
   setManageSceneFogEnd(1000);
   mGlobalFogDensityMultiplier = 1;
   mGlobalFogColourMultiplier = Ogre::ColourValue(1.0f, 1.0f, 1.0f, 1.0f);
   mSceneFogDensityMultiplier = 1;
   mSceneFogColourMultiplier = Ogre::ColourValue(0.7f, 0.7f, 0.7f, 0.7f);
   mGroundFogDensityMultiplier = 1;
   mGroundFogColourMultiplier = Ogre::ColourValue(1.0f, 1.0f, 1.0f, 1.0f);

   // Ambient lighting.
   setManageAmbientLight (true);
   setMinimumAmbientLight (Ogre::ColourValue (0.1f, 0.1f, 0.3f));
   mEnsureSingleLightSource = false;
   mEnsureSingleShadowSource = false;

   // Observer time & position. J2000 is midday.
   mObserverLatitude = Ogre::Degree(45);
   mObserverLongitude = Ogre::Degree(0);
   mpUniversalClock->setJulianDay(Astronomy::J2000);
}

void CaelumSystem::autoConfigure(CaelumComponent componentsToCreate/* = CAELUM_COMPONENTS_DEFAULT*/)
{
   // Clear everything; revert to default.
   clear();

   if (componentsToCreate == 0)
      return;  // Nothing to do. Don't print junk if not creating anything.

   LogManager::getSingleton ().logMessage ("Caelum: Creating caelum sub-components.");

   // Init skydome
   if (componentsToCreate & CAELUM_COMPONENT_SKY_DOME)
   {
      try
      {
         setSkyDome(new SkyDome(mSceneMgr, getCaelumCameraNode()));
      }
      catch (const Caelum::UnsupportedException& ex)
      {
         LogManager::getSingleton ().logMessage(
            "Caelum: Failed to initialize skydome: " + ex.getFullDescription());
      }
   }

   // Init sun
   if (componentsToCreate & CAELUM_COMPONENT_SUN)
   {
      try
      {
         setSun (new SpriteSun(mSceneMgr, getCaelumCameraNode()));
         getSun()->setAmbientMultiplier (Ogre::ColourValue (0.5f, 0.5f, 0.5f));
         getSun()->setDiffuseMultiplier (Ogre::ColourValue (3.f, 3.f, 2.7f));
         getSun()->setSpecularMultiplier (Ogre::ColourValue (5.f, 5.f, 5.f));

         getSun()->setAutoDisable (true);
         getSun()->setAutoDisableThreshold (0.05f);
      }
      catch (const Caelum::UnsupportedException& ex)
      {
         LogManager::getSingleton().logMessage("Caelum: Failed to initialize sun: " + ex.getFullDescription());
      }
   }

   // Init moon
   if (componentsToCreate & CAELUM_COMPONENT_MOON) {
      try {
         this->setMoon (new Moon (mSceneMgr, getCaelumCameraNode ()));
         this->getMoon ()->setAutoDisable (true);
         this->getMoon ()->setAutoDisableThreshold (0.05);
      } catch (Caelum::UnsupportedException& ex) {
         LogManager::getSingleton ().logMessage (
            "Caelum: Failed to initialize moon: " + ex.getFullDescription());
      }
   }
   if (componentsToCreate & CAELUM_COMPONENT_IMAGE_STARFIELD) {
      try {
         this->setImageStarfield (new ImageStarfield (mSceneMgr, getCaelumCameraNode ()));
      } catch (Caelum::UnsupportedException& ex) {
         LogManager::getSingleton ().logMessage (
            "Caelum: Failed to initialize the old image starfield: " + ex.getFullDescription());
      }
   }
   if (componentsToCreate & CAELUM_COMPONENT_POINT_STARFIELD) {
      try {
         this->setPointStarfield (new PointStarfield (mSceneMgr, getCaelumCameraNode ()));
      } catch (Caelum::UnsupportedException& ex) {
         LogManager::getSingleton ().logMessage (
            "Caelum: Failed to initialize starfield: " + ex.getFullDescription());
      }
   }
   if (componentsToCreate & CAELUM_COMPONENT_GROUND_FOG) {
      try {
         this->setGroundFog (new GroundFog (mSceneMgr, getCaelumCameraNode ()));
      } catch (Caelum::UnsupportedException& ex) {
         LogManager::getSingleton ().logMessage (
            "Caelum: Failed to initialize ground fog: " + ex.getFullDescription());
      }
   }
   if (componentsToCreate & CAELUM_COMPONENT_CLOUDS) {
      try {
         this->setCloudSystem (new CloudSystem (mSceneMgr, getCaelumGroundNode ()));
         getCloudSystem ()->createLayerAtHeight (3000);		
         getCloudSystem ()->getLayer (0)->setCloudCover (0.3);
      } catch (Caelum::UnsupportedException& ex) {
         LogManager::getSingleton ().logMessage (
            "Caelum: Failed to initialize clouds: " + ex.getFullDescription());
      }
   }
   if (componentsToCreate & CAELUM_COMPONENT_PRECIPITATION) {
      try {
         this->setPrecipitationController (new PrecipitationController (mSceneMgr));
      } catch (Caelum::UnsupportedException& ex) {
         LogManager::getSingleton ().logMessage (
            "Caelum: Failed to initialize precipitation: " + ex.getFullDescription());
      }
   }
   if (componentsToCreate & CAELUM_COMPONENT_SCREEN_SPACE_FOG) {
      try {
         this->setDepthComposer (new DepthComposer (mSceneMgr));
      } catch (Caelum::UnsupportedException& ex) {
         LogManager::getSingleton ().logMessage (
            "Caelum: Failed to initialize precipitation: " + ex.getFullDescription());
      }
   }

   LogManager::getSingleton ().logMessage ("Caelum: DONE initializing");
}


//-----------------------------------------------------------------------------
///
void CaelumSystem::shutdown (const bool cleanup)
{
   LogManager::getSingleton ().logMessage ("Caelum: Shutting down Caelum system...");
   destroySubcomponents (true);

   if (cleanup)
   {
      Ogre::Root::getSingleton().removeFrameListener(this);
      delete this;
   }
   else
   {
      // We'll delete later. Make sure we're registered as a frame listener, or we'd leak.
      Ogre::Root::getSingleton().addFrameListener(this);
      mCleanup = true;
   }
}

void CaelumSystem::attachViewportImpl (Ogre::Viewport* vp)
{
   LogManager::getSingleton().getDefaultLog ()->logMessage (
      "CaelumSystem: Attached to"
      " viewport " + StringConverter::toString ((long)vp) +
      " render target " + vp->getTarget ()->getName ());
   if (getAutoAttachViewportsToComponents ()) {
      if (getPrecipitationController ()) {
         getPrecipitationController ()->createViewportInstance (vp);
      }
      if (getDepthComposer ()) {
         getDepthComposer ()->createViewportInstance (vp);
      }
   }
}

void CaelumSystem::detachViewportImpl (Ogre::Viewport* vp)
{
   LogManager::getSingleton().getDefaultLog ()->logMessage (
      "CaelumSystem: Detached from "
      " viewport " + StringConverter::toString ((long)vp) +
      " render target " + vp->getTarget ()->getName ());
   if (getAutoAttachViewportsToComponents ()) {
      if (getPrecipitationController ()) {
         getPrecipitationController ()->destroyViewportInstance (vp);
      }
      if (getDepthComposer ()) {
         getDepthComposer ()->destroyViewportInstance (vp);
      }
   }
}


//-----------------------------------------------------------------------------
///
bool CaelumSystem::attachViewport (Ogre::Viewport *vp)
{
   if (!vp)
      return false;

   bool inserted = mAttachedViewports.insert(vp).second;
   if (inserted)
      attachViewportImpl(vp);

   return inserted;
}


//-----------------------------------------------------------------------------
///
bool CaelumSystem::detachViewport(Ogre::Viewport *vp)
{
   AttachedViewportSet::size_type erase_result = mAttachedViewports.erase(vp);
   assert(erase_result == 0 || erase_result == 1);
   bool found = erase_result == 1;
   if (found)
      detachViewportImpl (vp);

   return found;
}


//-----------------------------------------------------------------------------
///
void CaelumSystem::detachAllViewports()
{
   AttachedViewportSet::const_iterator it = mAttachedViewports.begin(), end = mAttachedViewports.end();
   while (it != end)
   {
      detachViewportImpl (*it);
      ++it;
   }
   mAttachedViewports.clear();
}

//-----------------------------------------------------------------------------
///
bool CaelumSystem::isViewportAttached (Ogre::Viewport* vp) const
{
   return mAttachedViewports.find(vp) != mAttachedViewports.end();
}


void CaelumSystem::setSkyDome (SkyDome *obj) {
   mSkyDome.reset (obj);
}

void CaelumSystem::setSun (BaseSkyLight* obj) {
   mSun.reset (obj);
}

void CaelumSystem::setMoon (Moon* obj) {
   mMoon.reset (obj);
}

void CaelumSystem::setImageStarfield (ImageStarfield* obj) {
   mImageStarfield.reset (obj);
}

void CaelumSystem::setPointStarfield (PointStarfield* obj) {
   mPointStarfield.reset (obj);
}

void CaelumSystem::setGroundFog (GroundFog* obj) {
   mGroundFog.reset (obj);
}

void CaelumSystem::setCloudSystem (CloudSystem* obj) {
   mCloudSystem.reset (obj);
}

void CaelumSystem::setPrecipitationController (PrecipitationController* newptr) {
   PrecipitationController* oldptr = getPrecipitationController ();
   if (oldptr == newptr) {
      return;
   }
   // Detach old
   if (getAutoAttachViewportsToComponents() && oldptr) {
      std::for_each (mAttachedViewports.begin(), mAttachedViewports.end(),
         std::bind1st (std::mem_fun (&PrecipitationController::destroyViewportInstance), oldptr));
   }
   // Attach new.
   if (getAutoAttachViewportsToComponents() && newptr) {
      std::for_each (mAttachedViewports.begin(), mAttachedViewports.end(),
         std::bind1st (std::mem_fun (&PrecipitationController::createViewportInstance), newptr));
   }
   mPrecipitationController.reset(newptr);
}

void CaelumSystem::setDepthComposer (DepthComposer* ptr) {
   mDepthComposer.reset(ptr);
   if (getAutoAttachViewportsToComponents() && getDepthComposer ()) {
      std::for_each (
         mAttachedViewports.begin(), mAttachedViewports.end(),
         std::bind1st (
         std::mem_fun (&DepthComposer::createViewportInstance),
         getDepthComposer ()));
   }
}

void CaelumSystem::preViewportUpdate (const Ogre::RenderTargetViewportEvent &e) {
   Ogre::Viewport *viewport = e.source;
   Ogre::Camera *camera = viewport->getCamera ();

   if (getAutoViewportBackground ()) {
      viewport->setBackgroundColour (Ogre::ColourValue::Black);
   }
   if (getAutoNotifyCameraChanged ()) {
      this->notifyCameraChanged (camera);
   }
}

void CaelumSystem::notifyCameraChanged(Ogre::Camera* cam)
{
   // Move camera node.
   if (getAutoMoveCameraNode())
   {
      mpCaelumCameraNode->setPosition(cam->getDerivedPosition());
      mpCaelumCameraNode->_update(true, true);
   }

   if (getSkyDome ()) {
      getSkyDome ()->notifyCameraChanged (cam);
   }

   if (getSun ()) {
      getSun ()->notifyCameraChanged (cam);
   }

   if (getMoon ()) {
      getMoon ()->notifyCameraChanged (cam);
   }

   if (getImageStarfield ()) {
      getImageStarfield ()->notifyCameraChanged (cam);
   }

   if (getPointStarfield ()) {
      getPointStarfield ()->notifyCameraChanged (cam);
   }

   if (getGroundFog ()) {
      getGroundFog ()->notifyCameraChanged (cam);
   }
}


//-----------------------------------------------------------------------------
///
bool CaelumSystem::frameStarted(const Ogre::FrameEvent &e)
{
   if (mCleanup)
   {
      // Delayed destruction.
      Ogre::Root::getSingleton().removeFrameListener (this);
      delete this;
      return true;
   }

   updateSubcomponents(e.timeSinceLastFrame);
   return true;
}


//-----------------------------------------------------------------------------
///
void CaelumSystem::updateSubcomponents(Ogre::Real timeSinceLastFrame)
{
   if (!mpUniversalClock)
   {
      assert(false && "CaelumSystem not initialized");
      return;
   }

   mpUniversalClock->update(timeSinceLastFrame);
   // Timing variables
   LongReal julDay = mpUniversalClock->getJulianDay();
   LongReal relDayTime = fmod(julDay, 1.);
   Real secondDiff = timeSinceLastFrame * mpUniversalClock->getTimeScale();

   // Get astronomical parameters.
   Ogre::Vector3 sunDir = getSunDirection(julDay);
   Ogre::Vector3 moonDir = getMoonDirection(julDay);  
   Real moonPhase = getMoonPhase(julDay);

   // Get parameters from sky colour model.
   Real fogDensity = getFogDensity(relDayTime, sunDir);           
   Ogre::ColourValue fogColour = getFogColour(relDayTime, sunDir);                  
   Ogre::ColourValue sunLightColour = getSunLightColour(relDayTime, sunDir);
   Ogre::ColourValue sunSphereColour = getSunSphereColour(relDayTime, sunDir);
   Ogre::ColourValue moonLightColour = getMoonLightColour(moonDir);
   Ogre::ColourValue moonBodyColour = getMoonBodyColour(moonDir); 

   fogDensity *= mGlobalFogDensityMultiplier;
   fogColour = fogColour * mGlobalFogColourMultiplier;

   // Update image starfield
   if (getImageStarfield())
   {
      getImageStarfield()->update (relDayTime);
      getImageStarfield()->setInclination (-getObserverLatitude ());
   }

   // Update point starfield
   if (getPointStarfield ())
   {
      getPointStarfield()->setObserverLatitude (getObserverLatitude ());
      getPointStarfield()->setObserverLongitude (getObserverLongitude ());
      getPointStarfield()->_update (relDayTime);
   }

   // Update skydome.
   if (getSkyDome ())
   {
      getSkyDome()->setSunDirection (sunDir);
      getSkyDome()->setHazeColour (fogColour * mSceneFogColourMultiplier);
   }

   // Update scene fog.
   if (mManageSceneFogMode != Ogre::FOG_NONE)
   {
      mSceneMgr->setFog (mManageSceneFogMode,
         fogColour * mSceneFogColourMultiplier,
         fogDensity * mSceneFogDensityMultiplier, 
         mManageSceneFogFromDistance,
         mManageSceneFogToDistance);
   }

   // Update ground fog.
   if (getGroundFog ())
   {
      getGroundFog ()->setColour (fogColour * mGroundFogColourMultiplier);
      getGroundFog ()->setDensity (fogDensity * mGroundFogDensityMultiplier);
   }

   // Update sun
   if (getSun ())
   {
      mSun->update (sunDir, sunLightColour, sunSphereColour);
   }

   // Update moon.
   if (getMoon ())
   {
      mMoon->update (
         moonDir,
         moonLightColour,
         moonBodyColour);
      mMoon->setPhase (moonPhase);
   }

   // Update clouds
   if (getCloudSystem ())
   {
      getCloudSystem ()->update (
         secondDiff, sunDir, sunLightColour, fogColour, sunSphereColour);
   }

   // Update precipitation
   if (getPrecipitationController ())
   {
      getPrecipitationController ()->update (secondDiff, fogColour);
   }

   // Update screen space fog
   if (getDepthComposer ())
   {
      getDepthComposer ()->update ();
      getDepthComposer ()->setSunDirection (sunDir);
      getDepthComposer ()->setHazeColour (fogColour);
      getDepthComposer ()->setGroundFogColour (fogColour * mGroundFogColourMultiplier);
      getDepthComposer ()->setGroundFogDensity (fogDensity * mGroundFogDensityMultiplier);
   }

   // Update ambient lighting.
   if (getManageAmbientLight ())
   {
      Ogre::ColourValue ambient = Ogre::ColourValue::Black;
      if (getMoon ()) {
         ambient += getMoon ()->getLightColour () * getMoon ()->getAmbientMultiplier ();
      }
      if (getSun ()) {
         ambient += getSun ()->getLightColour () * getSun ()->getAmbientMultiplier ();
      }
      ambient.r = std::max(ambient.r, mMinimumAmbientLight.r);
      ambient.g = std::max(ambient.g, mMinimumAmbientLight.g);
      ambient.b = std::max(ambient.b, mMinimumAmbientLight.b);
      ambient.a = std::max(ambient.a, mMinimumAmbientLight.a);

      mSceneMgr->setAmbientLight (ambient);
   }

   if (getSun() && getMoon ())
   {
      Ogre::Real moonBrightness = moonLightColour.r + moonLightColour.g + moonLightColour.b + moonLightColour.a;
      Ogre::Real sunBrightness = sunLightColour.r + sunLightColour.g + sunLightColour.b + sunLightColour.a;
      bool sunBrighterThanMoon = (sunBrightness > moonBrightness);

      if (getEnsureSingleLightSource ())
      {
         getMoon()->setForceDisable(sunBrighterThanMoon);
         getSun ()->setForceDisable(!sunBrighterThanMoon);
      }

      if (getEnsureSingleShadowSource())
      {
         getMoon()->getMainLight ()->setCastShadows(!sunBrighterThanMoon);
         getSun ()->getMainLight ()->setCastShadows(sunBrighterThanMoon);
      }
   }
}

void CaelumSystem::setManageSceneFog (Ogre::FogMode v) {
   mManageSceneFogMode = v;

   // Prevent having some stale values around.
   // also important: we need to initialize this before using any terrain
   mSceneMgr->setFog (mManageSceneFogMode);
}

void CaelumSystem::disableFogMangement()
{
   // NO RESET
   mManageSceneFogMode = Ogre::FOG_NONE;
}

Ogre::FogMode CaelumSystem::getManageSceneFog () const {
   return mManageSceneFogMode;
}

void CaelumSystem::setManageSceneFogStart (Ogre::Real from) {
   mManageSceneFogFromDistance = from;
}

Ogre::Real CaelumSystem::getManageSceneFogStart () const {
   return mManageSceneFogFromDistance;
}

void CaelumSystem::setManageSceneFogEnd (Ogre::Real from) {
   mManageSceneFogToDistance = from;
}

Ogre::Real CaelumSystem::getManageSceneFogEnd () const {
   return mManageSceneFogToDistance;
}

void CaelumSystem::setSceneFogDensityMultiplier (Real value) {
   mSceneFogDensityMultiplier = value;
}

Real CaelumSystem::getSceneFogDensityMultiplier () const {
   return mSceneFogDensityMultiplier;
}

void CaelumSystem::setGroundFogDensityMultiplier (Real value) {
   mGroundFogDensityMultiplier = value;
}

Real CaelumSystem::getGroundFogDensityMultiplier () const {
   return mGroundFogDensityMultiplier;
}

void CaelumSystem::setGlobalFogDensityMultiplier (Real value) {
   mGlobalFogDensityMultiplier = value;
}

Real CaelumSystem::getGlobalFogDensityMultiplier () const {
   return mGlobalFogDensityMultiplier;
}

void CaelumSystem::setSkyGradientsImage (const Ogre::String &filename) {
   mSkyGradientsImage.reset(new Ogre::Image ());
   mSkyGradientsImage->load (filename, RESOURCE_GROUP_NAME);
}

void CaelumSystem::setSunColoursImage (const Ogre::String &filename) {
   mSunColoursImage.reset(new Ogre::Image ());
   mSunColoursImage->load (filename, RESOURCE_GROUP_NAME);
}

Ogre::ColourValue CaelumSystem::getFogColour (Real time, const Ogre::Vector3 &sunDir) {
   if (!mSkyGradientsImage.get()) {
      return Ogre::ColourValue::Black;
   }

   Real elevation = sunDir.dotProduct (Ogre::Vector3::UNIT_Y) * 0.5 + 0.5;
   Ogre::ColourValue col = InternalUtilities::getInterpolatedColour (elevation, 1, mSkyGradientsImage.get(), false);
   return col;
}

Real CaelumSystem::getFogDensity (Real time, const Ogre::Vector3 &sunDir)
{
   if (!mSkyGradientsImage.get()) {
      return 0;
   }

   Real elevation = sunDir.dotProduct (Ogre::Vector3::UNIT_Y) * 0.5 + 0.5;
   Ogre::ColourValue col = InternalUtilities::getInterpolatedColour (elevation, 1, mSkyGradientsImage.get(), false);
   return col.a;
}

Ogre::ColourValue CaelumSystem::getSunSphereColour (Real time, const Ogre::Vector3 &sunDir)
{
   if (!mSunColoursImage.get())
      return Ogre::ColourValue::White;

   Real elevation = sunDir.dotProduct (Ogre::Vector3::UNIT_Y);
   elevation = elevation * 2.f + 0.4f;
   return InternalUtilities::getInterpolatedColour (elevation, 1.f, mSunColoursImage.get(), false);
}

ColourValue CaelumSystem::getSunLightColour (Real time, const Ogre::Vector3 &sunDir)
{
   if (!mSkyGradientsImage.get())
   {
      //exit(-1); // <-- m2codeGEN (SVA) What the fuck is that?
      assert(false);
      return Ogre::ColourValue::White;
   }

   Real elevation = sunDir.dotProduct (Vector3::UNIT_Y) * 0.5f + 0.5f;

   // Hack: return averaged sky colours.
   // Don't use an alpha value for lights, this can cause nasty problems.
   Ogre::ColourValue col = InternalUtilities::getInterpolatedColour (elevation, elevation, mSkyGradientsImage.get(), false);
   float val = (col.r + col.g + col.b) * 0.3333333333f;
   col = Ogre::ColourValue(val, val, val, 1.f);
   assert(Ogre::Math::RealEqual(col.a, 1.f, std::numeric_limits<float>::epsilon()));

   return col;
}

Ogre::ColourValue CaelumSystem::getMoonBodyColour (const Ogre::Vector3 &moonDir) {
   return Ogre::ColourValue::White;
}

Ogre::ColourValue CaelumSystem::getMoonLightColour (const Ogre::Vector3 &moonDir)
{
   if (!mSkyGradientsImage.get())
   {
      return Ogre::ColourValue::Blue;
   }

   // Scaled version of getSunLightColor
   Real elevation = moonDir.dotProduct (Ogre::Vector3::UNIT_Y) * 0.5f + 0.5f;
   Ogre::ColourValue col = InternalUtilities::getInterpolatedColour (elevation, elevation, mSkyGradientsImage.get(), false);
   float val = (col.r + col.g + col.b) * 0.3333333333f;
   val /= 2.5f;

   col = Ogre::ColourValue(val, val, val, 1.f);
   assert(Ogre::Math::RealEqual(col.a, 1.f, std::numeric_limits<float>::epsilon()));

   return col;
}

Ogre::Vector3 CaelumSystem::makeDirection(Ogre::Degree azimuth, Ogre::Degree altitude)
{
   return Ogre::Vector3(
      Ogre::Math::Sin (azimuth) * Ogre::Math::Cos (altitude),     // East
      -Ogre::Math::Sin (altitude),                                // Zenith
      -Ogre::Math::Cos (azimuth) * Ogre::Math::Cos (altitude));   // North 
}

const Ogre::Vector3 CaelumSystem::getSunDirection (LongReal jday)
{
   Ogre::Degree azimuth, altitude;
   {
      ScopedHighPrecissionFloatSwitch precissionSwitch;

      Astronomy::getHorizontalSunPosition(jday,
         getObserverLongitude(), getObserverLatitude(),
         azimuth, altitude);		
   }
   Ogre::Vector3 res = makeDirection(azimuth, altitude);

   return res;
}

const Ogre::Vector3 CaelumSystem::getMoonDirection (LongReal jday)
{
   Ogre::Degree azimuth, altitude;
   {
      ScopedHighPrecissionFloatSwitch precissionSwitch;

      Astronomy::getHorizontalMoonPosition(jday,
         getObserverLongitude (), getObserverLatitude (),
         azimuth, altitude);
   }	
   Ogre::Vector3 res = makeDirection(azimuth, altitude);

   return res;
}

const Ogre::Real CaelumSystem::getMoonPhase (LongReal jday)
{
   // Calculates julian days since January 22, 2008 13:36 (full moon)
   // and divides by the time between lunations (synodic month)
   LongReal T = (jday - 2454488.0665L) / 29.531026L;

   T = fabs(fmod(T, 1));
   return -fabs(-4 * T + 2) + 2;
}

void CaelumSystem::forceSubcomponentQueryFlags (uint flags)
{
   if (getSkyDome ()) getSkyDome ()->setQueryFlags (flags);
   if (getSun ()) getSun ()->setQueryFlags (flags);
   if (getMoon ()) getMoon ()->setQueryFlags (flags);
   if (getImageStarfield ()) getImageStarfield ()->setQueryFlags (flags);
   if (getPointStarfield ()) getPointStarfield ()->setQueryFlags (flags);        
   if (getGroundFog ()) getGroundFog ()->setQueryFlags (flags);
   if (getCloudSystem ()) getCloudSystem ()->forceLayerQueryFlags (flags);
}

void CaelumSystem::forceSubcomponentVisibilityFlags (uint flags)
{
   if (getSkyDome ()) getSkyDome ()->setVisibilityFlags (flags);
   if (getSun ()) getSun ()->setVisibilityFlags (flags);
   if (getMoon ()) getMoon ()->setVisibilityFlags (flags);
   if (getImageStarfield ()) getImageStarfield ()->setVisibilityFlags (flags);
   if (getPointStarfield ()) getPointStarfield ()->setVisibilityFlags (flags);        
   if (getGroundFog ()) getGroundFog ()->setVisibilityFlags (flags);
   if (getCloudSystem ()) getCloudSystem ()->forceLayerVisibilityFlags (flags);
}

